﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HighThroughput.Common
{
    public class Modbus
    {
        /*
         * 读寄存器数据帧，读线圈状态，
         * startAddr:起始地址
         * regCount:数据长度，寄存器数量
         * funcode:功能码：0x01 读线圈状态 ,0x03 读保持型寄存器
         */
        public static List<byte> readFreameRTU(byte stationid, byte funcode, ushort startAddr, ushort regCount)
        {
            List<byte> frame = new List<byte>();

            // byte[] 需要指定长度
            frame.Add(stationid);// 1号从站
            frame.Add(funcode);// 功能码：0x01 读线圈状态 ,0x03 读保持型寄存器
            // 起始地址
            frame.Add(BitConverter.GetBytes(startAddr)[1]);//
            frame.Add(BitConverter.GetBytes(startAddr)[0]);
            // 寄存器数量，读取数量
            frame.Add(BitConverter.GetBytes(regCount)[1]);
            frame.Add(BitConverter.GetBytes(regCount)[0]);

            // CRC校验
            frame = CRC16(frame);


            return frame;
        }

        //写寄存器
        public static List<byte> writeFreameRTU(byte stationid, byte funcode, ushort startAddr, byte[] bytesData)
        {

            List<byte> frame = new List<byte>();

            // byte[] 需要指定长度
            frame.Add(stationid);// 1号从站
            frame.Add(funcode);// 功能码：
            // 起始地址
            frame.Add(BitConverter.GetBytes(startAddr)[1]);//
            frame.Add(BitConverter.GetBytes(startAddr)[0]);
            // 数据字长
            short dataLength = (short)(bytesData.Length / 2);
            frame.Add(BitConverter.GetBytes(dataLength)[1]);
            frame.Add(BitConverter.GetBytes(dataLength)[0]);
            //数据字长长度，一般为2
            frame.Add((byte)(0x01 * (ushort)bytesData.Length));
            //数据区
            for (int i = bytesData.Length - 1; i >= 0; i--)
            {
                frame.Add(bytesData[i]);
            }

            // CRC校验
            frame = CRC16(frame);


            return frame;
        }


        /*
      * 读寄存器数据帧，读线圈状态，
      * startAddr:起始地址
      * regCount:寄存器数量
      * funcode:功能码：0x04
      */
        public static List<byte> readFreameTCP(ushort startAddr, ushort regCount, byte funcode)
        {
            List<byte> frame = new List<byte>();

            //报文头MBAP
            /*消息号，0位：用来表示类型 0：读 1：写
             *      1位：用来表示起始地址，最大255
             * 便于与返回值进行对应，读和读的返回值
            */
            // 0-1流水号
            frame.Add(0x01);
            frame.Add(0x00);
            //2-3必须都为0，代表这是modbus 通信
            frame.Add(0x00);
            frame.Add(0x00);
            //4-5 数据长度 ，5之后的字节数，可以后面再赋值
            frame.Add(BitConverter.GetBytes(regCount)[1]);
            frame.Add(BitConverter.GetBytes(regCount)[0]);
            //6 站号，似乎没什么意义
            frame.Add(0x00);
            //7 功能码
            frame.Add(funcode);// 功能码：0x01 读线圈状态 ,0x03 读保持型寄存器
            //8-9 寄存器起始地址
            frame.Add(BitConverter.GetBytes(startAddr)[1]);//
            frame.Add(BitConverter.GetBytes(startAddr)[0]);
            //10-11寄存器数量
            frame.Add(BitConverter.GetBytes(regCount)[1]);
            frame.Add(BitConverter.GetBytes(regCount)[0]);

            return frame;
        }


        //CRC校验
        static List<byte> CRC16(List<byte> value, ushort poly = 0xA001, ushort crcInit = 0xFFFF)
        {
            if (value == null || !value.Any())
                throw new ArgumentException("");

            //运算
            ushort crc = crcInit;
            for (int i = 0; i < value.Count; i++)
            {
                crc = (ushort)(crc ^ (value[i]));
                for (int j = 0; j < 8; j++)
                {
                    crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ poly) : (ushort)(crc >> 1);
                }
            }
            byte hi = (byte)((crc & 0xFF00) >> 8);  //高位置
            byte lo = (byte)(crc & 0x00FF);         //低位置

            List<byte> buffer = new List<byte>();
            //添加校验原始值
            buffer.AddRange(value);
            //添加校验位值
            buffer.Add(lo);
            buffer.Add(hi);

            //加上原始校验值返回
            return buffer;
        }
    }
}
